/* Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/* test
$ ip0region -d ip.merge.txt -i /tmp/ip0region.image

$ ip0region -i /tmp/ip0region.image --ip 183.158.184.117

$ ip0region -d ip.merge.txt -i /tmp/ip0region.image --benchmark-test-round-count 10
ip[0.0.0.0] city[内网IP] isp[内网IP] - first query of 683844 ip's from round 1
ip[0.0.0.0] city[内网IP] isp[内网IP] - first query of 683844 ip's from round 2
ip[0.0.0.0] city[内网IP] isp[内网IP] - first query of 683844 ip's from round 3
ip[0.0.0.0] city[内网IP] isp[内网IP] - first query of 683844 ip's from round 4
ip[0.0.0.0] city[内网IP] isp[内网IP] - first query of 683844 ip's from round 5
ip[0.0.0.0] city[内网IP] isp[内网IP] - first query of 683844 ip's from round 6
ip[0.0.0.0] city[内网IP] isp[内网IP] - first query of 683844 ip's from round 7
ip[0.0.0.0] city[内网IP] isp[内网IP] - first query of 683844 ip's from round 8
ip[0.0.0.0] city[内网IP] isp[内网IP] - first query of 683844 ip's from round 9
ip[0.0.0.0] city[内网IP] isp[内网IP] - first query of 683844 ip's from round 10
--------- BENCHMARK RESULT ---------
           total count : 13676880
           elapse time : 7.063026 (s)
timeval of every query : 516 (ns)
*/

#include "rbtree.h"
#include "byteorder.h"
#include "wbyte.h"

#include "rbtree_tpl.h"

#include "ip0region_in.h"

#define __IP0REGION_VERSION			"1.0.0.0"

struct RegionInfo
{
	char			*country_name ;
	uint32_t		country_name_len ;
	char			*area_name ;
	uint32_t		area_name_len ;
	char			*province_name ;
	uint32_t		province_name_len ;
	char			*city_name ;
	uint32_t		city_name_len ;
	char			*isp_name ;
	uint32_t		isp_name_len ;
	
	struct rb_node		region_info_tree_node ;
	
	uint32_t		region_offset ;
} ;

struct IpRegionInfo
{
	char			*start_ip_str ;
	uint32_t		start_ip ;
	char			*end_ip_str ;
	uint32_t		end_ip ;
	struct RegionInfo	region_info ;
	struct RegionInfo	*p_ref_region_info ;
	
	struct rb_node		ip_region_info_tree_node ;
} ;

struct IpRegionContext
{
	struct rb_root		ip_region_info_tree ;
	struct rb_root		region_info_tree ;
	
	size_t			region_section_size ;
	size_t			ip_count ;
	size_t			ip_section_size ;
} ;

LINK_RBTREENODE_INT( LinkIpRegionInfo , struct IpRegionContext , ip_region_info_tree , struct IpRegionInfo , ip_region_info_tree_node , start_ip )
TRAVEL_RBTREENODE( TravelIpRegionInfo , struct IpRegionContext , ip_region_info_tree , struct IpRegionInfo , ip_region_info_tree_node )

int CompareRegionInfo( void *pv1 , void *pv2 )
{
	struct RegionInfo	*region_info_1 = (struct RegionInfo *) pv1 ;
	struct RegionInfo	*region_info_2 = (struct RegionInfo *) pv2 ;
	int			nret = 0 ;
	
	nret = strcmp( region_info_1->country_name , region_info_2->country_name ) ;
	if( nret )
		return nret;
	
	nret = strcmp( region_info_1->area_name , region_info_2->area_name ) ;
	if( nret )
		return nret;
	
	nret = strcmp( region_info_1->province_name , region_info_2->province_name ) ;
	if( nret )
		return nret;
	
	nret = strcmp( region_info_1->city_name , region_info_2->city_name ) ;
	if( nret )
		return nret;
	
	nret = strcmp( region_info_1->isp_name , region_info_2->isp_name ) ;
	if( nret )
		return nret;
	
	return 0;
}
LINK_RBTREENODE_RETURNDUPLICATE( LinkRegionInfo , struct IpRegionContext , region_info_tree , struct RegionInfo , region_info_tree_node , & CompareRegionInfo )
TRAVEL_RBTREENODE( TravelRegionInfo , struct IpRegionContext , region_info_tree , struct RegionInfo , region_info_tree_node )

char *StrdupEntireIpRegionFile( char *pathfilename , size_t *p_file_size );

static int BenchmarkTest( struct IpRegionContext *ip_region_ctx , char *image_filename , size_t benchmark_test_round_count )
{
	struct Ip0regionContext	*ctx = NULL ;
	size_t			benchmark_test_no ;
	size_t			ip_no ;
	struct IpRegionInfo	*ip_region_info = NULL ;
	char			*start_ip_str = NULL ;
	char			*end_ip_str = NULL ;
	char			*country_name = NULL ;
	char			*area_name = NULL ;
	char			*province_name = NULL ;
	char			*city_name = NULL ;
	char			*isp_name = NULL ;
	struct timeval		start_tv ;
	struct timeval		end_tv ;
	struct timeval		diff_tv ;
	uint64_t		diff_microseconds ;
	size_t			total_count ;
	int			nret = 0 ;
	
	nret = LoadIp0regionImage( image_filename , & ctx ) ;
	if( nret )
	{
		printf( "*** ERROR : load ip0region image[%s] failed[%d] , errno[%d]\n" , image_filename , nret , errno );
		return nret;
	}
	
	gettimeofday( & start_tv , NULL );
	
	for( benchmark_test_no = 0 ; benchmark_test_no < benchmark_test_round_count ; benchmark_test_no++ )
	{
		ip_no = 0 ;
		ip_region_info = NULL ;
		for( ; ; )
		{
			ip_region_info = TravelIpRegionInfo( ip_region_ctx , ip_region_info ) ;
			if( ip_region_info == NULL )
				break;
			ip_no++;
			
			nret = QueryIp0regionImage( ctx , ip_region_info->start_ip_str , & start_ip_str , & end_ip_str , & country_name , & area_name , & province_name , & city_name , & isp_name ) ;
			if( nret )
			{
				printf( "*** ERROR : query ip0region image by ip[%s] failed[%d]\n" , ip_region_info->start_ip_str , nret );
				FreeIp0regionImage( & ctx );
				return nret;
			}
			
			if( ip_no == 1 )
			{
				printf( "ip[%s] city[%s] isp[%s] - first query of %zu ip's from round %zu\n" , ip_region_info->start_ip_str , city_name , isp_name , ip_region_ctx->ip_count , benchmark_test_no+1 );
			}
			
			nret = QueryIp0regionImage( ctx , ip_region_info->end_ip_str , & start_ip_str , & end_ip_str , & country_name , & area_name , & province_name , & city_name , & isp_name ) ;
			if( nret )
			{
				printf( "*** ERROR : query ip0region image by ip[%s] failed[%d]\n" , ip_region_info->end_ip_str , nret );
				FreeIp0regionImage( & ctx );
				return nret;
			}
		}
	}
	
	total_count = benchmark_test_round_count * ip_no * 2 ;
	gettimeofday( & end_tv , NULL );
	diff_tv.tv_sec = end_tv.tv_sec - start_tv.tv_sec ;
	diff_tv.tv_usec = end_tv.tv_usec - start_tv.tv_usec ;
	if( diff_tv.tv_usec < 0 )
	{
		diff_tv.tv_sec--;
		diff_tv.tv_usec += 1000000 ;
	}
	diff_microseconds = diff_tv.tv_sec * 1000000 + diff_tv.tv_usec ;
	printf( "--------- BENCHMARK RESULT ---------\n" );
	printf( "           total count : %zu\n" , total_count );
	printf( "           elapse time : %ld.%06ld (s)\n" , diff_tv.tv_sec , diff_tv.tv_usec );
	printf( "timeval of every query : %ld (ns)\n" , diff_microseconds*1000 / total_count );
	
	FreeIp0regionImage( & ctx );
	
	return 0;
}

static int AnalysisTextAndDumpImageFile( char *data_filename , char *image_filename , size_t benchmark_test_round_count )
{
	char			*data_file_buffer = NULL ;
	size_t			data_file_buffer_len ;
	char			*data_file_buffer_end = NULL ;
	
	struct IpRegionContext	ip_region_ctx ;
	
	struct IpRegionInfo	*ip_region_info = NULL ;
	char			*field_base = NULL ;
	char			*p = NULL ;
	int			wbyte_len ;
	
	struct RegionInfo	*region_info = NULL ;
	size_t			region_offset ;
	int			i ;
	
	FILE			*fp = NULL ;
	uint8_t			u8_1 ;
	uint8_t			u8_2 ;
	uint8_t			u8_3 ;
	uint8_t			u8_4 ;
	uint8_t			u8_5 ;
	uint16_t		u16 ;
	uint32_t		u32 ;
	uint32_t		u32_1 ;
	uint32_t		u32_2 ;
	uint32_t		u32_3 ;
	
	size_t			write_size ;
	size_t			verify_size ;
	
	int			nret = 0 ;
	
	memset( & ip_region_ctx , 0x00 , sizeof(struct IpRegionContext) );
	
	data_file_buffer = StrdupEntireIpRegionFile( data_filename , & data_file_buffer_len ) ;
	if( data_file_buffer == NULL )
	{
		printf( "*** ERROR : read file '%s' failed\n" , data_filename );
		return 1;
	}
	data_file_buffer_end = data_file_buffer + data_file_buffer_len - 1 ;
	
	p = data_file_buffer ;
	for( ; ; )
	{
		ip_region_info = (struct IpRegionInfo *)malloc( sizeof(struct IpRegionInfo) ) ;
		if( ip_region_info == NULL )
		{
			printf( "*** ERROR : malloc failed , errno[%d]\n" , errno );
			return 1;
		}
		memset( ip_region_info , 0x00 , sizeof(struct IpRegionInfo) );
		
#define ANALYSIS_IP(_field_name_,_sep_char_,_set_member_) \
		field_base = p ; \
		while( p <= data_file_buffer_end ) \
		{ \
			wbyte_len = EvaluateOneWbyteLength( WBYTE_CHARSET_GB18030 , (unsigned char *)p , (unsigned char *)data_file_buffer_end ) ; \
			if( wbyte_len <= 0 ) \
			{ \
				printf( "*** ERROR : analysis wbyte failed , offset[%ld]\n" , p-data_file_buffer ); \
				return 1; \
			} \
			\
			if( wbyte_len == 1 && (*p) == _sep_char_ ) \
			{ \
				struct in_addr		ip_addr ; \
				\
				(*p) = '\0' ; \
				nret = inet_aton( (const char *)field_base , & ip_addr ) ; \
				if( nret != 1 ) \
				{ \
					printf( "*** ERROR : inet_aton[%s] failed\n" , field_base ); \
					return 1; \
				} \
				\
				ip_region_info->_set_member_##_str = field_base ; \
				ip_region_info->_set_member_ = NTOH32( ip_addr.s_addr ) ; \
				\
				p++; \
				break; \
			} \
			else \
			{ \
				p += wbyte_len ; \
			} \
		} \
		if( p > data_file_buffer_end ) \
		{ \
			printf( "*** ERROR : analysis %s overflow\n" , _field_name_ ); \
			return 1; \
		}
		
		/* start ip */
		ANALYSIS_IP( "start ip" , '|' , start_ip )
		
		/* end ip */
		ANALYSIS_IP( "end ip" , '|' , end_ip )
		
#define ANALYSIS_STRING(_sep_char_,_set_member_) \
		field_base = p ; \
		while( p <= data_file_buffer_end ) \
		{ \
			wbyte_len = EvaluateOneWbyteLength( WBYTE_CHARSET_GB18030 , (unsigned char *)p , (unsigned char *)data_file_buffer_end ) ; \
			if( wbyte_len <= 0 ) \
			{ \
				printf( "*** ERROR : analysis wbyte failed , offset[%ld]\n" , p-data_file_buffer ); \
				return 1; \
			} \
			\
			if( wbyte_len == 1 && (*p) == _sep_char_ ) \
			{ \
				(*p) = '\0' ; \
				ip_region_info->region_info._set_member_##_len = p - field_base ; \
				if( ip_region_info->region_info._set_member_##_len >= 255 ) \
				{ \
					printf( "*** ERROR : %s[%d][%.*s] too long\n" , #_set_member_ , ip_region_info->region_info._set_member_##_len , ip_region_info->region_info._set_member_##_len,field_base ); \
					return 1; \
				} \
				if( ip_region_info->region_info._set_member_##_len == 1 && field_base[0] == '0' ) \
				{ \
					ip_region_info->region_info._set_member_ = "" ; \
					ip_region_info->region_info._set_member_##_len = 0 ; \
				} \
				else \
				{ \
					ip_region_info->region_info._set_member_ = field_base ; \
					/* \
					if( ip_region_info->region_info._set_member_ == NULL ) \
					{ \
						printf( "*** ERROR : strndup failed , errno[%d]\n" , errno ); \
						return 1; \
					} \
					*/ \
				} \
				\
				p++; \
				break; \
			} \
			else \
			{ \
				p += wbyte_len ; \
			} \
		} \
		
		/* country name */
		ANALYSIS_STRING( '|' , country_name )
		if( p > data_file_buffer_end )
		{
			printf( "*** ERROR : analysis country name overflow\n" );
			return 1;
		}
		
		/* area name */
		ANALYSIS_STRING( '|' , area_name )
		if( p > data_file_buffer_end )
		{
			printf( "*** ERROR : analysis area name overflow\n" );
			return 1;
		}
		
		/* province name */
		ANALYSIS_STRING( '|' , province_name )
		if( p > data_file_buffer_end )
		{
			printf( "*** ERROR : analysis province name overflow\n" );
			return 1;
		}
		
		/* city name */
		ANALYSIS_STRING( '|' , city_name )
		if( p > data_file_buffer_end )
		{
			printf( "*** ERROR : analysis city name overflow\n" );
			return 1;
		}
		
		/* isp name */
		ANALYSIS_STRING( '\n' , isp_name )
		nret = LinkIpRegionInfo( & ip_region_ctx , ip_region_info ) ;
		if( nret )
		{
			printf( "*** ERROR : add ip region failed[%d]\n" , nret );
			return 1;
		}
		if( p > data_file_buffer_end )
		{
			break;
		}
	}
	
#if 0
	{
		int	i = 0 ;
		ip_region_info = NULL ;
		for( ; ; )
		{
			ip_region_info = TravelIpRegionInfo( & ip_region_ctx , ip_region_info ) ;
			if( ip_region_info == NULL )
				break;
			i++;
			
			printf( "%d : [%s][%"PRIu32"] [%s][%"PRIu32"] [%.*s] [%.*s] [%.*s] [%.*s] [%.*s]\n"
				, i
				, ip_region_info->start_ip_str,ip_region_info->start_ip
				, ip_region_info->end_ip_str,ip_region_info->end_ip
				, ip_region_info->region_info.country_name_len,ip_region_info->region_info.country_name
				, ip_region_info->region_info.area_name_len,ip_region_info->region_info.area_name
				, ip_region_info->region_info.province_name_len,ip_region_info->region_info.province_name
				, ip_region_info->region_info.city_name_len,ip_region_info->region_info.city_name
				, ip_region_info->region_info.isp_name_len,ip_region_info->region_info.isp_name );
		}
	}
#endif
	
	ip_region_ctx.ip_count = 0 ;
	ip_region_info = NULL ;
	for( ; ; )
	{
		ip_region_info = TravelIpRegionInfo( & ip_region_ctx , ip_region_info ) ;
		if( ip_region_info == NULL )
			break;
		
		ip_region_ctx.ip_count++;
		
		region_info = LinkRegionInfo( & ip_region_ctx , & (ip_region_info->region_info) ) ;
		if( region_info == NULL )
			ip_region_info->p_ref_region_info = & (ip_region_info->region_info) ;
		else
			ip_region_info->p_ref_region_info = region_info ;
	}
	ip_region_ctx.ip_section_size = ip_region_ctx.ip_count * IP0REGION_V1_IPBLOCK_SIZE ;
	
	if( benchmark_test_round_count > 0 )
	{
		nret = BenchmarkTest( & ip_region_ctx , image_filename , benchmark_test_round_count ) ;
		free( data_file_buffer );
		return -nret;
	}
	
	i = 0 ;
	region_offset = IP0REGION_V1_FILEHEADER_SIZE ;
	region_info = NULL ;
	for( ; ; )
	{
		region_info = TravelRegionInfo( & ip_region_ctx , region_info ) ;
		if( region_info == NULL )
			break;
		i++;
		
		region_info->region_offset = region_offset ;
		region_offset += IP0REGION_V1_REGIONHEADER_SIZE + region_info->country_name_len+1 + region_info->area_name_len+1 + region_info->province_name_len+1 + region_info->city_name_len+1 + region_info->isp_name_len+1 ;
		
#if 0
		printf( "%d : [%.*s] [%.*s] [%.*s] [%.*s] [%.*s]\n"
			, i
			, region_info->country_name_len,region_info->country_name
			, region_info->area_name_len,region_info->area_name
			, region_info->province_name_len,region_info->province_name
			, region_info->city_name_len,region_info->city_name
			, region_info->isp_name_len,region_info->isp_name );
#endif
	}
	ip_region_ctx.region_section_size = region_offset - IP0REGION_V1_FILEHEADER_SIZE ;
	
	fp = fopen( image_filename , "w" ) ;
	if( fp == NULL )
	{
		printf( "*** ERROR : open file '%s' failed\n" , image_filename );
		free( data_file_buffer );
		return 1;
	}
	
	fwrite( IP0REGION_FILEHEADER_MAGIC_STRING , IP0REGION_V1_MAGIC_LENGTH , 1 , fp );
	u16 = HTON16( 0 ) ;
	fwrite( & u16 , IP0REGION_V1_REVERVED_LENGTH , 1 , fp );
	u16 = HTON16( 1 ) ;
	fwrite( & u16 , IP0REGION_V1_VERSION_LENGTH , 1 , fp );
	u32 = HTON32( ip_region_ctx.region_section_size ) ;
	fwrite( & u32 , IP0REGION_V1_REGION_SECTION_LENGTH , 1 , fp );
	u32 = HTON32( ip_region_ctx.ip_section_size ) ;
	fwrite( & u32 , IP0REGION_V1_IP_SECTION_LENGTH , 1 , fp );
	
	write_size = ftell( fp ) ;
	verify_size = IP0REGION_V1_FILEHEADER_SIZE ;
	if( write_size != verify_size )
	{
		printf( "*** ERROR : verify file header size failed , write_size[%zu] verify_size[%zu]\n" , write_size , verify_size );
		fclose( fp );
		free( data_file_buffer );
		return 1;
	}
	
	region_info = NULL ;
	for( ; ; )
	{
		region_info = TravelRegionInfo( & ip_region_ctx , region_info ) ;
		if( region_info == NULL )
			break;
		
		u8_1 = region_info->country_name_len ;
		u8_2 = region_info->area_name_len ;
		u8_3 = region_info->province_name_len ;
		u8_4 = region_info->city_name_len ;
		u8_5 = region_info->isp_name_len ;
		fwrite( & u8_1 , sizeof(u8_1) , 1 , fp );
		fwrite( & u8_2 , sizeof(u8_2) , 1 , fp );
		fwrite( & u8_3 , sizeof(u8_3) , 1 , fp );
		fwrite( & u8_4 , sizeof(u8_4) , 1 , fp );
		fwrite( & u8_5 , sizeof(u8_5) , 1 , fp );
		fwrite( region_info->country_name , region_info->country_name_len+1 , 1 , fp );
		fwrite( region_info->area_name , region_info->area_name_len+1 , 1 , fp );
		fwrite( region_info->province_name , region_info->province_name_len+1 , 1 , fp );
		fwrite( region_info->city_name , region_info->city_name_len+1 , 1 , fp );
		fwrite( region_info->isp_name , region_info->isp_name_len+1 , 1 , fp );
	}
	
	write_size = ftell( fp ) - IP0REGION_V1_FILEHEADER_SIZE ;
	verify_size = ip_region_ctx.region_section_size ;
	if( write_size != verify_size )
	{
		printf( "*** ERROR : verify region section size failed , write_size[%zu] verify_size[%zu]\n" , write_size , verify_size );
		fclose( fp );
		free( data_file_buffer );
		return 1;
	}
	
	ip_region_info = NULL ;
	for( ; ; )
	{
		ip_region_info = TravelIpRegionInfo( & ip_region_ctx , ip_region_info ) ;
		if( ip_region_info == NULL )
			break;
		
		u32_1 = HTON32( ip_region_info->start_ip ) ;
		u32_2 = HTON32( ip_region_info->end_ip ) ;
		u32_3 = HTON32( ip_region_info->p_ref_region_info->region_offset ) ;
		fwrite( & u32_1 , sizeof(u32_1) , 1 , fp );
		fwrite( & u32_2 , sizeof(u32_2) , 1 , fp );
		fwrite( & u32_3 , sizeof(u32_3) , 1 , fp );
	}
	
	write_size = ftell( fp ) - IP0REGION_V1_FILEHEADER_SIZE - ip_region_ctx.region_section_size ;
	verify_size = ip_region_ctx.ip_section_size ;
	if( write_size != verify_size )
	{
		printf( "*** ERROR : verify ip section size failed , write_size[%zu] verify_size[%zu]\n" , write_size , verify_size );
		fclose( fp );
		free( data_file_buffer );
		return 1;
	}
	
	fclose( fp );
	
	free( data_file_buffer );
	
	return 0;
}

static int QueryIpInImage( char *image_filename , char *ip_str )
{
	struct Ip0regionContext	*ctx = NULL ;
	char			*start_ip_str = NULL ;
	char			*end_ip_str = NULL ;
	char			*country_name = NULL ;
	char			*area_name = NULL ;
	char			*province_name = NULL ;
	char			*city_name = NULL ;
	char			*isp_name = NULL ;
	int			nret = 0 ;
	
	nret = LoadIp0regionImage( image_filename , & ctx ) ;
	if( nret )
	{
		printf( "*** ERROR : load ip0region image[%s] failed[%d] , errno[%d]\n" , image_filename , nret , errno );
		return nret;
	}
	
	nret = QueryIp0regionImage( ctx , ip_str , & start_ip_str , & end_ip_str , & country_name , & area_name , & province_name , & city_name , & isp_name ) ;
	if( nret )
	{
		printf( "*** ERROR : query ip0region image by ip[%s] failed[%d]\n" , ip_str , nret );
		FreeIp0regionImage( & ctx );
		return nret;
	}
	
	printf( "      ip : %s\n" , ip_str );
	printf( "start_ip : %s\n" , start_ip_str );
	printf( "  end_ip : %s\n" , end_ip_str );
	printf( " country : %s\n" , country_name );
	printf( "    area : %s\n" , area_name );
	printf( "province : %s\n" , province_name );
	printf( "    city : %s\n" , city_name );
	printf( "     isp : %s\n" , isp_name );
	
	FreeIp0regionImage( & ctx );
	
	return 0;
}

void usage()
{
	printf( "ip0region v%s\n" , __IP0REGION_VERSION );
	printf( "USAGE : ip0region -d (from_data_filename) -i (to_image_filename)\n" );
	printf( "                  -i (image_filename) --ip (x.x.x.x)\n" );
	return;
}

int main( int argc , char *argv[] )
{
	int			a ;
	char			*data_filename = NULL ;
	char			*image_filename = NULL ;
	char			*ip_str = NULL ;
	size_t			benchmark_test_round_count = 0 ;
	
	if( argc == 1 )
	{
		usage();
		return 0;
	}
	
	for( a = 1 ; a < argc ; a++ )
	{
		if( STRCMP( argv[a] , == , "-d" ) )
		{
			a++;
			if( a > argc )
			{
				printf( "*** ERROR : expect parameter after '-d'\n" );
				return 7;
			}
			
			data_filename = argv[a] ;
		}
		else if( STRCMP( argv[a] , == , "-i" ) )
		{
			a++;
			if( a > argc )
			{
				printf( "*** ERROR : expect parameter after '-i'\n" );
				return 7;
			}
			
			image_filename = argv[a] ;
		}
		else if( STRCMP( argv[a] , == , "--benchmark-test-round-count" ) )
		{
			a++;
			if( a > argc )
			{
				printf( "*** ERROR : expect parameter after '-i'\n" );
				return 7;
			}
			
			benchmark_test_round_count = atol(argv[a]) ;
		}
		else if( STRCMP( argv[a] , == , "--ip" ) )
		{
			a++;
			if( a > argc )
			{
				printf( "*** ERROR : expect parameter after '-i'\n" );
				return 7;
			}
			
			ip_str = argv[a] ;
		}
		else
		{
			printf( "*** ERROR : unexpect '%s'\n" , argv[a] );
			return 7;
		}
	}
	
	if( data_filename && image_filename )
	{
		return -AnalysisTextAndDumpImageFile( data_filename , image_filename , benchmark_test_round_count );
	}
	else if( image_filename && ip_str )
	{
		return -QueryIpInImage( image_filename , ip_str );
	}
	else
	{
		usage();
		return 7;
	}
}

